#pragma once

#include <queue>
#include <cstdint>
#include <ctime>
#include <stdexcept>
#include <thread>
#include "util/allocator.hh"
#include "spsc_queue.hpp"
#include "object_pool.hpp"

#ifdef KRATOS_COMPILER_LOG_OFF

#define TIMER_QUEUE_LOG_ERROR(a, b)

#define TIMER_QUEUE_LOG_EXCEPTION(e, a, b)

#define TIMER_QUEUE_PARAM_FAIL_RETURN_FALSE(a)\
    return false;

#define TIMER_QUEUE_PARAM_FAIL_RETURN(a, b)\
    return b;

#else

#define TIMER_QUEUE_LOG_ERROR(a, b)\
    logwcom("TimerQueue") << a << b << logend;

#define TIMER_QUEUE_LOG_EXCEPTION(e, a, b)\
    logwcom("TimerQueue") << "Exception:" << e.what() <<","<< a << b << logend;

#define TIMER_QUEUE_PARAM_FAIL_RETURN_FALSE(a)\
    if (a) {\
        logwcom("TimerQueue") << "[" << __FILE__ << ":" << __LINE__ << "]:" << #a << ", check failed" << logend;\
        return false;\
    }

#define TIMER_QUEUE_PARAM_FAIL_RETURN(a, b)\
    if (a) {\
        logwcom("TimerQueue") << "[" << __FILE__ << ":" << __LINE__ << "]:" << #a << ", check failed" << logend;\
        return b;\
    }

#endif // KRATOS_COMPILER_LOG_OFF

namespace kratos {  namespace corelib {

using TimerID = std::size_t; // timer ID type defination
constexpr static TimerID INVALID_TIMER_ID = 0; // default timer ID

// Timer
class Timer {
public:
    // ctor
    // @param timeout the duration of timer in millionseconds
    // @param trigger the absolute timestamp in millionseconds that triggers the timer
    // @param timerID timer's ID
    // @param period the timer will be triggerred forever if 'true' or triggerred only once
    Timer(std::time_t timeout, std::time_t trigger, std::uint32_t timerID, bool period);
    // dtor
    ~Timer();
    // reset the trigger timestamp
    // @param current current timestamp in millionseconds
    void reset(std::time_t current);
    // returns timer's deadline in millionseconds
    std::time_t getTrigger() const;
    // returns timer ID
    TimerID getID() const;
    // Is periodical?
    bool isPeriod() const;
private:
    std::time_t _timeout; // The period of timer
    time_t _trigger; // The deadline of timer
    TimerID _timerID; // timer ID
    bool _period; // Is it periodical
};

/**
 * Timer Queue
 * 
 * Runs in a standalone thread to check timer's timeout
 * invoke checkExpired() to pump timer ID of timeout timer.
 */
class TimerQueue {
    // The thread event of timer
    enum class TimerEvent : std::uint8_t {
        EVENT_START = 1, // forevent
        EVENT_DEADLINE, // only once
        EVENT_CANCEL, // cancel
    };

    // The thread event class
    struct Event {
        std::uint8_t id; // event ID @sa TimerEvent
        TimerID timerID; // timer ID
        std::time_t timeout; // timer's interval
        std::time_t deadline; // the timestamp to trigger the timer
        
    };
    // The customized comparation operator for std::priority_queue
    // - Min heap
    struct TimerCompare {
        bool operator()(const Timer* a, const Timer* b) {
            return a->getTrigger() > b->getTrigger();
        }
    };
    using PriorityQueue = kratos_priority_queue<Timer*, TimerCompare>; // min heap
    PriorityQueue priorQueue_; // The min stack
    using TimerIDSet = UNORDERED_SET(std::uint32_t, TimerQueue_timerIDSet_);
    TimerIDSet timerIDSet_; // The set of timer ID
    SPSCQueue<Event*> eventInput_; // Timer event input queue
    struct ExpireEvent {
        TimerID timerID;
        bool period;
    };
    SPSCQueue<ExpireEvent> waitOutput_; // Timer timeout event queue
    _ObjectPool<Event> eventPool_; // The object pool for 'Event'
    _ObjectPool<Timer> timerPool_; // The object pool for 'Timer'
    std::thread routine_; // The standalone thread for checkingg expired timer
    bool run_; // The running flag for worker thread
    TimerID timerIDStart_; // The starter of timer ID

public:
    // ctor
    TimerQueue();
    // dtor
    ~TimerQueue();
    // start timer thread
    bool start();
    // stop timer thread
    bool stop();
    // pumps a expired timer's ID 
    TimerID checkExpired();
    // pumps a expired timer's ID 
    // @param isPeriod [OUT] Is periodical timer?
    TimerID checkExpired(bool& isPeriod);
    // Starts a periodical timer
    // @param timeout The duration of timer in millionseconds
    // @param current Current timestamp in millionseconds
    // @retval INVALID_TIMER_ID fail
    // @retval The valid timer ID
    TimerID startTimer(std::time_t timeout, std::time_t current);
    // Starts a timer
    // @param timeout The duration of timer in millionseconds
    // @param current Current timestamp in millionseconds
    // @retval INVALID_TIMER_ID fail
    // @retval The valid timer ID
    TimerID startTimerOnce(std::time_t timeout, std::time_t current);
    // Cancel the timer
    // @param id timer ID
    // @retval true
    // @retval false fail
    bool cancel(TimerID id);
    // returns the start flag
    bool isStarted();

private:
    // process input event queue in worker thread
    void processInputEvent(std::time_t now);
    // process expired timer in worker thread
    void processTimer(std::time_t now);
    // Returns new timer ID
    TimerID getNewTimerID();
};

}}
